/** @file    SessionManager.cpp
 *  @author  Alessandro Polo
 *  @version $Id: SessionManager.cpp 2883 2010-08-09 14:51:56Z alex $
 *  @brief
 * File containing methods for the wosh::SessionManager class.
 * The header for this class can be found in SessionManager.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <core/SessionManager.h>
 #include <core/ObjectFactory.h>
 #include <core/List.h>
 #include <core/Table.h>
 #include <core/MethodsCommon.h>
 #include <core/MessageFilter.h>
 #include <core/Utilities.h>
 #include <core/NetworkManager.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>
 #include <core/SecurityManager.h>
 #include <core/UserManager.h>
 #include <core/DataModels.h>
 #include <core/Notification.h>
 #include <core/Communicator.h>
 #include <core/Functional.h>
 #include <interfaces/services/CommunicatorService.h>


using namespace std;
namespace wosh {

// Gamma singleton! static reference is allocated and maintained by WoshKernel
extern "C" {
	SessionManager* SessionManager::getInstance() { 
		if ( WoshKernel::getInstance() == NULL ) return NULL;
		return &WoshKernel::getInstance()->sessions();
	}
}
 // register as abstract because it is a singleton:
 WOSH_REGISTER_INTERFACE(wosh::SessionManager, "wosh::WoshModule", 0.01, _static_SessionManager1 )
 WOSH_REGISTER_INTERFACE(wosh::SessionManager, "wosh::interfaces::services::CommunicatorService", 0.01, _static_SessionManager2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

SessionManager::SessionManager( const WoshKernel* parent )
	: WoshModule(parent, _SessionManager_NAME, _SessionManager_URI) {

//Log.setLevel( VERBOSE );

	this->protocolDefault = NULL;

	Log(LOG_DEBUG, " Initializing Properties.." );
	Properties.set( _CommunicatorService_KEY_ChannelType,	(int)Notification::TYPE_TEXTUAL, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_ChannelLink,	(int )Communicator::LINK_SYNCH, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Cost,			0.0,Permission::RW_R__R__() );
	Properties.set( _CommunicatorService_KEY_Priority,		50, Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Reliability,	70, Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Protocol,		(int)Notification::PROTOCOL_SHELL + (int)Notification::PROTOCOL_HUMAN, Permission::RW_R__R__() );

	Log(LOG_DEBUG, " Registering Methods.." );
	MethodList* mmListSessions = Methods.create<MethodList>( _METHOD_List, "List Sessions", Permission::R_XR_XR__() );
	mmListSessions->setDataFieldExtractor( DataFieldExtractor<Session>::createFor< DataAdapterMapT<long, Session> >(this->sessions, "Sessions") );

	MethodList* mmListSessionProtocols = Methods.create<MethodList>( _SessionManager_METHOD_ListProtocols, "List SessionProtocols", Permission::R_XR_XR__() );
	mmListSessionProtocols->setDataFieldExtractor( DataFieldExtractor<SessionProtocol>::createFor< DataAdapterListT<SessionProtocol> >(this->protocols, "Session Protocols") );
 }

SessionManager::~SessionManager() {
	Log(LOG_DEBUG, ":~SessionManager() Destroying.." );
	///@bug BUG todo stop sessions
	Log(LOG_VERBOSE, ":~SessionManager() Freeing Sessions [%d]..", this->sessions.count() );
	this->sessions.clear();
	Log(LOG_VERBOSE, ":~SessionManager() Freeing Protocols [%d]..", this->protocols.count() );
	this->protocols.clear();
	Log(LOG_VERBOSE, " Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// KERNEL EVENTS

WRESULT SessionManager::onKernelInitializing() {
	WRESULT ret = WoshModule::onKernelInitializing();
	Interfaces.add("wosh::interfaces::services::CommunicatorService");
	this->protocolDefault = ObjectFactory::createTypeOf<SessionProtocol>(false);
	if ( this->protocolDefault == NULL )
		Log(LOG_WARNING, ":onKernelInitializing() Can't allocate [any] default SessionProtocol!" );
	else
		this->protocols.push_back(this->protocolDefault);
	BusSecurity.setMessageHandler(this);
	WRESULT bus_d_connected = BusSecurity.connect( _Bus_Security );
	if ( WFAILED(bus_d_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "init", "FAILED#%d Connecting Bus ("_Bus_Security")..", bus_d_connected );
	return ret;
 }

WRESULT SessionManager::onKernelStopping() {
	WRESULT ret = WRET_OK;
	tLongSessionMap::ConstIterator it;
	const tLongSessionMap::ConstIterator it_end = this->sessions.end();
	for ( it=this->sessions.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		it->second->printMessage( Utilities::format("Kernel %s is Shutting down NOW..", WoshKernel::getKernelName()) );
	 }
	ret += WoshModule::onKernelStopping();
	return ret;
 }

WRESULT SessionManager::onKernelStopped() {
	WRESULT ret = WRET_OK;
	tLongSessionMap::ConstIterator it;
	const tLongSessionMap::ConstIterator it_end = this->sessions.end();
	for ( it=this->sessions.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		ret += it->second->close();
	 }
	ret += WoshModule::onKernelStopped();
	return ret;
 }

/////////////////////////////////////////////////////////////////////////////////////////////// KERNEL EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SessionManager::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( message.getSourceBus() == _Bus_Security ) return;
	WoshModule::busMessage(message, source);
 }
/*
void SessionManager::evalNotification( const Message& message, const Bus* source ) {
	if ( message.getDestination().isBroadcast() ) return;
	if ( message.isEmpty() ) return;
	const Notification* notif = message.getContent()->asNotification();
	if ( notif == NULL ) return;
	if ( notif->getProtocol() != Notification::PROTOCOL_SHELL ) {
		Log(LOG_VERBOSE, ":evalNotification() : INVALID Protocol [%d] (Resuming..)", (int)notif->getProtocol() );
	 }
	WRESULT sent = WRET_ERR_PARAM;
	if ( notif->isForAnyone() || notif->getRecipent_User().size() == 0 ) {
		//any session
		Log(LOG_VERBOSE, ":evalNotification() : Notification for any user.." );
		forwardMessageForSession(0, message, source );
		sent = WRET_OK;
	 }
	else if ( notif->getRecipent_User() != "wosh" ) {
		// a notification for a custom user.. probably..
		// shoulnd't be parsed by me, since i'm a communicator FOR the WOSH system itself,
		// the only output to the user is the LOCAL STD SHELL (if was loaded)
		this->sessions.transactionBeginRead();
		tLongSessionMap::ConstIterator it;
		const tLongSessionMap::ConstIterator it_end = this->sessions.end();
		for ( it=this->sessions.begin(); it!=it_end; ++it ) {
			if ( it->second == NULL ) continue;
			if ( it->second->getUsername() == notif->getRecipent_User() ) continue;
			it->second->busMessage(message, source);
			sent = WRET_OK;
		 }
		this->sessions.transactionEnd();
	 }
	else {
		// target user is WOSH..
		// this is a special case, we are using notification(s) as transport layer for I/O
		// input notification (this flow) are routed by CommunicationManager (or a customized Communicator)
		// output notification are created/handled by wosh::communication::SessionIM
		// Session messaging I/O (command request/response) works as any other session

		// we have to identify the mapped session.
		// an user may have MORE sessions opened, since a session is
		int found = 0;
		this->sessions.transactionBeginRead();
		tLongSessionMap::ConstIterator it;
		const tLongSessionMap::ConstIterator it_end = this->sessions.end();
		for ( it=this->sessions.begin(); it!=it_end; ++it ) {
			if ( it->second == NULL ) continue;
			if ( it->second->getUsername() != notif->getSender_User() ) continue;
			if ( !notif->getPreferredCommunicator().isEmpty() && it->second->getCreatorURI() != notif->getPreferredCommunicator() ) continue;
			it->second->busMessage(message, source);
			++found;
		 }
		if ( found == 0 ) {
			Session* sessionIM = ObjectFactory::createTypeOf<Session>("wosh::communication::SessionIM", 0.0);
			if ( sessionIM != NULL ) {
				sessionIM->setUsername( notif->getSender_User() );
				sessionIM->setCreatorURI(message.getSource());
				sent = registerSession_( sessionIM, true );
				if ( WFAILED(sent) )
					Log(LOG_CRITICAL, ":evalNotification() : FAILED#%d (inline) Registering new SessionIM!", sent );
				sessionIM->busMessage(message, source);
			 }
			else {
				SystemError::raise(this, Log, SystemError::CRITICAL, 0, "evalNotification", "Can't allocatate SessionIM.." );
				sent = WRET_ERR_INTERNAL;
			 }
		 }
		else
			sent = WRET_OK;
		this->sessions.transactionEnd();
	 }
	Message* reply = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_sent, new Variant(notif->getID()), sent ) );
	signMessage(reply);
	//reply->setSecurityToken( generateCredentials() );
	BusCore.postMessage(reply);
	if ( WSUCCEEDED(sent) ) {
		Message* reply2 = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_read, new Variant(notif->getID()), WRET_OK ) );
		signMessage(reply2);
		//reply2->setSecurityToken( generateCredentials() );
		BusCore.postMessage(reply2);
	 }
 }

void SessionManager::forwardMessageForSession( long id, const Message& message, const Bus* source ) {
	this->sessions.transactionBeginRead();
	if ( id == 0 ) {
		Log(LOG_VERBOSE, ":forwardMessageForSession() : Message#%"PRId64" for ANY Session", message.getID(), id );
		tLongSessionMap::ConstIterator it;
		const tLongSessionMap::ConstIterator it_end = this->sessions.end();
		for ( it=this->sessions.begin(); it!=it_end; ++it ) {
			if ( it->second == NULL ) continue;
			it->second->busMessage(message, source);
		 }
		this->sessions.transactionEnd();
		return;
	 }
	Log(LOG_VERBOSE, ":forwardMessageForSession() : Message#%"PRId64" for Session#%ld..", message.getID(), id );
	Session* session = this->sessions.find(id);
	if ( session == NULL ) {
		Log(LOG_CRITICAL, ":forwardMessageForSession() FAILED : Session#%ld not found!", id );
		this->sessions.transactionEnd();
		return;
	 }
	session->busMessage(message, source);
	this->sessions.transactionEnd();
 }
*/
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
/*
bool SessionManager::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	if ( property_current.getKey() == _SessionManager_KEY_AutoAllocSessionIM ) {
		this->autoAllocSessionIM = value_proposed.toBoolean(this->autoAllocSessionIM);
		return true;
	 }
	else // maybe is a (common) WoshModule property, let base implementation process it
		return WoshModule::updatingProperty(do_update, value_proposed, property_current, source);
 }
*/
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SessionManager::registerSession( Session* session_instance ) {
	if ( session_instance == NULL ) return WRET_ERR_PARAM;
	MutexLockerWrite mL(this->sessions.getMutex());
	return this->registerSession_( session_instance, true );
 }

WRESULT SessionManager::registerSession_( Session* session, bool raise_event ) {
	if ( session == NULL ) return WRET_ERR_PARAM;
	if ( this->sessions.exists(session->getID()) ) {
		SystemError::raise(this, Log, SystemError::WARNING, 0, "registerSession", "WARNING#%d Session#%ld exists!", WRET_ERR_PARAM, session->getID() );
		return WRET_ERR_PARAM;
	 }
	if ( this->sessions.existsValue(session) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "registerSession", "FAILED#%d Session INSTANCE of #%ld already registered (with different name!?)!", WRET_ERR_PARAM, session->getID() );
		return WRET_ERR_PARAM;
	 }
	if ( !session->getURI().isGlobal() ) {
		Log(LOG_DEBUG, ":registerSession(%ld) Overwriting URI..", session->getID() );
		session->setParentObject(this);
		session->getURI().setParent( getURI() );
		session->getURI().setName( Utilities::toString<long>(session->getID()) );
		session->getURI().registerGlobal();
	 }
	this->sessions.set( session->getID(), session );
	session->registered();
	Log(LOG_INFO, ":registerSession() Registered Session#%ld", session->getID() );
	if ( raise_event ) {
		Fact* fact = new Session_ListChanged(session->getID(), Session_ListChanged::EVENT_LISTITEM_ADDED );
		Message* msg_event = new Message(fact);
		msg_event->setDestinationBroadcast(true);
		signMessage(msg_event);
		BusCore.postMessage(msg_event);
	 }
	return WRET_OK;
 }

WRESULT SessionManager::unRegisterSession( Session* session_instance ) {
	if ( session_instance == NULL ) return WRET_ERR_PARAM;
	MutexLockerWrite mL(this->sessions.getMutex());
	return this->unRegisterSession_( session_instance, true );
 }

WRESULT SessionManager::unRegisterSession_( Session* session, bool raise_event ) {
	if ( session == NULL ) return WRET_ERR_PARAM;

	if ( !this->sessions.exists(session->getID()) ) {
		SystemError::raise(this, Log, SystemError::WARNING, 0, "unRegisterSession", "WARNING#%d Session##%ld not found!", WRET_ERR_PARAM, session->getID() );
	 }
	if ( !this->sessions.existsValue(session) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "unRegisterSession", "FAILED#%d Session INSTANCE not found!", WRET_ERR_PARAM, session->getID() );
		return WRET_ERR_PARAM;
	 }
	this->sessions.erase(session->getID(), false);
	if ( raise_event ) {
		Fact* fact = new Session_ListChanged(session->getID(), Session_ListChanged::EVENT_LISTITEM_REMOVED );
		Message* msg_event = new Message(fact);
		msg_event->setDestinationBroadcast(true);
		signMessage(msg_event);
		BusCore.postMessage(msg_event);
	 }
	session->unregistered();

//	WRESULT ret = WRET_OK;
//	if ( session->isOpened() )
//		ret = session->close();

	//Log(LOG_INFO, ":unRegisterSession() Session '%s' unregistered", session->getID().c_str() );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// SESSION-EVENTS

void SessionManager::session_event( Session::SESSION_STATE state, Session* source ) {
	(void)state; (void)source;
 }

WRESULT SessionManager::session_post_message( Message* message, Session* source_session ) {
	if ( source_session == NULL ) return WRET_ERR_PARAM;
	message->setSource(source_session);
	if ( !message->hasSecurityToken() )
		SecurityManager::getCredentialImplicit().signMessage(message, source_session->getUsername());
	BusCore.postMessage( message );
	return WRET_OK;
 }

WRESULT SessionManager::session_authorize( const std::string& wosh_username, const SecurityTokenProvider* credentials, Session* source_session ) {
	if ( source_session == NULL ) return WRET_ERR_PARAM;
	if ( wosh_username == User::root ) {
		if ( !source_session->getRestriction(Session::ALLOW_ROOT) ) {
			return WRET_ERR_SECURITY;
		 }
	 }
	return UserManager::isAuthorized( wosh_username, credentials );
 }

SessionProtocol* SessionManager::getDefaultProtocol() {
	if ( getInstance() == NULL ) return NULL;
	return getInstance()->protocolDefault;
 }

////////////////////////////////////////////////////////////////////////////////////////////// SESSION-EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR-SERVICE

bool SessionManager::isUserRegistered( const std::string& username ) {
	return UserManager::isUser(username);
 }

long SessionManager::getUserLastSeen( const std::string& username ) {
	long ts = 0;
	this->sessions.transactionBeginRead();
	tLongSessionMap::ConstIterator it_f = std::find_if(this->sessions.begin(), this->sessions.end(),
		Functor_Validator<Session, const std::string&>(username, &Session::getUsername ) );
	if ( it_f != this->sessions.end() ) {
		this->sessions.transactionBeginRead();
		return ts;
	 }
	ts = it_f->second->getLastInputTS();
	this->sessions.transactionEnd();
	return ts; 
 }

bool SessionManager::isUserReachable( const std::string& username ) {
	return ( getUserLastSeen(username) > 0 );
 }

WRESULT SessionManager::sendMessageToUser( const std::string& userWosh, const std::string& message ) {
	WOSH_DEPRECATE("SessionManager::sendMessageToUser", "void")
	(void)userWosh; (void)message;
	return WRET_NOT_IMPLEMENTED;
 }
WRESULT SessionManager::sendMessageTo( const std::string& userID, const std::string& message ) {
	return sendMessageToUser(userID, message);
 }
WRESULT SessionManager::notify( const Notification* notification ) {
	WOSH_DEPRECATE("SessionManager::notify", "void")
	(void)notification;
	return WRET_NOT_IMPLEMENTED;
 }

//////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR-SERVICE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
